/**
 * PE文件解析类，用于解析并获取PE文件（可执行文件）的各种信息，如节头部、资源目录表等。
 * @author tongxin
 * @date 2024/4/17 22:34
 */
import { DosHeader } from "./DosHeader.ts";
import { NtHeader } from "./NtHeader.ts";
import { SectionHeaders } from "./SectionHeaders.ts";
import { ResourceDirectoryTable } from "./ResourceDirectoryTable.ts";
import { PEFileReader } from "./io/PEFileReader.ts";
import { FileHeader } from "./FileHeader.ts";

import { ResourceDirectoryEntry } from "./ResourceDirectoryEntry.ts";
import type { ResourceDataEntry } from "./ResourceDataEntry.ts";
import { ResourceTypes } from "./constants/ResourceTypes.ts";
import { IconEntry } from './IconEntry.ts'
import { IconGroup } from './IconGroup.ts'


export class PEFile {

  /**
   * 用于读取文件数据的接口
   */
  dataReader: IPEFileReader;
  /**
   * DOS头
   */
  dosHeader?: DosHeader;
  /**
   * NT头
   */
  ntHeader?: NtHeader;
  /**
   * 节头部集合
   */
  sectionHeaders?: SectionHeaders;
  /**
   * 资源目录表
   */
  resourceDirectoryTable?: ResourceDirectoryTable;
  iconGroups: IconGroup[] = []

  /**
   * 构造函数，初始化PE文件的各种头部信息和资源目录表。
   *
   * @param file 要解析的PE文件
   * @throws IOException 当读取文件时发生IO异常
   */
  constructor(file: File) {
    this.dataReader = new PEFileReader(file);
  }

  async read() {
    this.dosHeader = new DosHeader(await this.dataReader.read(DosHeader.DOS_HEADER_SIZE));
    this.dataReader.setOffset(this.dosHeader.addressOfNewExeHeader);
    this.ntHeader = new NtHeader(await this.dataReader.read(NtHeader.LENGTH));

    this.dataReader.setOffset(this.dosHeader.addressOfNewExeHeader + NtHeader.NT_HEADER_MAGIC_LENGTH + FileHeader.FILE_HEADER_SIZE + this.ntHeader.fileHeader.sizeOfOptionalHeader)
    console.log("sectionHeaders read start")
    this.sectionHeaders = new SectionHeaders(this.dataReader);
    await this.sectionHeaders.read(this.ntHeader.fileHeader.numberOfSections);
    this.dataReader.setOffset(this.sectionHeaders.get(SectionHeaders.RSRC).pointerToRawData)

    this.resourceDirectoryTable = new ResourceDirectoryTable(this.dataReader);
    await this.resourceDirectoryTable.read();
  }

  public async getIcons(): Promise<IconEntry[]> {
    if (!this.iconGroups.length) {
      await this.initIconGroups()
    }
    const iconEntries = this.getResourceDataEntriesById(
      ResourceTypes.RT_ICON
    )
    const icons: IconEntry[] = []

    for (let iconGroup of this.iconGroups) {
      for (let entry of iconGroup.entries) {
        const dataEntry = iconEntries[entry.id! - 1]
        const offset = this.sectionHeaders!.sectionHeaders[SectionHeaders.RSRC].pointerToRawData +
          dataEntry.dataRva -
          this.sectionHeaders!.sectionHeaders[SectionHeaders.RSRC].virtualAddress
        this.dataReader.setOffset(offset)
        const icon = new IconEntry(this.dataReader, iconGroup, entry,dataEntry.dataSize)
        await icon.read()
        icons.push(icon)
      }
    }
    return icons
  }

  async initIconGroups() {
    this.iconGroups = []
    const entries = this.getResourceDataEntriesById(
      ResourceTypes.RT_GROUP_ICON
    )
    for (const dataEntry of entries) {
      const offset =
        this.sectionHeaders!.sectionHeaders[SectionHeaders.RSRC].pointerToRawData +
        dataEntry.dataRva -
        this.sectionHeaders!.sectionHeaders[SectionHeaders.RSRC].virtualAddress
      this.dataReader.setOffset(offset)
      const iconGroup = new IconGroup(this.dataReader)
      await iconGroup.read()
      this.iconGroups.push(iconGroup)
    }
  }


  public getResourceDataEntriesById(id: number) {
    const entry: ResourceDirectoryEntry = this.resourceDirectoryTable!.entries[id]
    if (!entry) {
      throw new Error('没有找到信息:' + id)
    }
    return this.getResourceDataEntriesByEntry(entry)
  }

  getResourceDataEntriesByEntry(
    entry: ResourceDirectoryEntry
  ): ResourceDataEntry[] {
    if (entry.isLeaf()) {
      return (entry.resourceDataEntry && [entry.resourceDataEntry]) || []
    }
    if (entry.resourceDirectoryTable) {
      const entries = entry.resourceDirectoryTable.entries
      const dataEntries: ResourceDataEntry[] = []
      Object.values(entries).forEach((entry) => {
        dataEntries.push(...this.getResourceDataEntriesByEntry(entry))
      })
      return dataEntries
    }
    return []
  }


}
